iT邦幫忙

2026 iThome 鐵人賽

DAY 29
0
Build on Google AI

30 天玩轉 Google AI 全家桶:初學者的隨身 Coding 助理養成記系列 第 29 篇

Day 29:讓成果不只停在終端機!把 DevPulse 審查 JSON 放進 Stitch 儀表板

  • 分享至 

  • xImage
  •  

昨天替 DevPulse 做了 Prompt Injection 防護測試,確認程式碼註解裡的「忽略前面規則」不應該變成新的指令。不過,安全地完成分析只是其中一步。當 codeReviewFlow 找出問題後,目前最直接的查看方式仍是讀取終端機或 JSON;如果要快速掌握整個檔案的健康狀態,還需要一個更容易閱讀的畫面。

Day 18 已經用 Stitch 做出 DevPulse 儀表板,Day 19 也把產生的 HTML 搬回本機。今天就把兩邊接起來:不重新設計介面,也不增加新的 AI 流程,只把審查 JSON 放進現有頁面。

今天要完成三件事:

  1. 整理儀表板需要的 JSON 欄位。
  2. 讓原本的 Stitch 頁面讀取資料並產生問題清單。
  3. 在瀏覽器驗收數量、文字內容與安全顯示方式。

一、先替儀表板準備一份資料

前端不需要知道 Gemini 是怎麼分析程式碼的,只需要知道要顯示哪些資料。因此,我先準備一份適合展示的 report.json。以下是示範資料,實際發布時應換成 Day 23 的 codeReviewFlow 真正輸出;如果既有輸出的欄位名稱不同,只要在轉換時對應一次即可,不必為了 UI 重寫後端流程。

在 devpulse/web/ 新增 report.json:

{
  "file": "samples/sql.cjs",
  "generatedAt": "2026-10-13 14:30",
  "findings": [
    {
      "line": 3,
      "category": "security",
      "reason": "使用字串串接 SQL,輸入內容可能改變查詢語意。",
      "suggestion": "改用參數化查詢,將使用者輸入與 SQL 語法分離。"
    },
    {
      "line": 8,
      "category": "logic",
      "reason": "目前的處理方式沒有涵蓋預期的邊界情況。",
      "suggestion": "補上邊界條件判斷,並使用既有單元測試重新驗證。"
    }
  ]
}

這個格式刻意只保留儀表板真正要用的資訊:審查檔案、產生時間,以及每個問題的行號、分類、原因和建議。不要把整份原始程式碼塞進摘要卡片,否則畫面會很快失去重點。

二、在 Stitch 頁面加上資料顯示區

打開先前匯出的 devpulse/web/index.html。保留原本由 Stitch 生成的側邊欄、配色和排版,只在適當位置加入以下區塊;如果既有頁面已經有統計卡片,也可以直接替現有元素加上相同的 id,不必把整頁換掉。

<section class="mt-8 space-y-6">
  <div class="grid grid-cols-1 gap-4 md:grid-cols-3">
    <article class="rounded-2xl border border-slate-700 bg-slate-900 p-5">
      <p class="text-sm text-slate-400">審查檔案</p>
      <p id="report-file" class="mt-2 break-all text-lg font-semibold text-white">載入中…</p>
    </article>

    <article class="rounded-2xl border border-slate-700 bg-slate-900 p-5">
      <p class="text-sm text-slate-400">發現問題</p>
      <p id="issue-count" class="mt-2 text-3xl font-bold text-white">—</p>
    </article>

    <article class="rounded-2xl border border-slate-700 bg-slate-900 p-5">
      <p class="text-sm text-slate-400">資安問題</p>
      <p id="security-count" class="mt-2 text-3xl font-bold text-rose-300">—</p>
    </article>
  </div>

  <div class="rounded-2xl border border-slate-700 bg-slate-900 p-5">
    <div class="flex flex-wrap items-center justify-between gap-3">
      <h2 class="text-xl font-semibold text-white">審查明細</h2>
      <p id="report-time" class="text-sm text-slate-400"></p>
    </div>
    <p id="report-status" class="mt-3 text-sm text-slate-400" role="status">正在載入報告…</p>
    <div id="findings-list" class="mt-4 space-y-3"></div>
  </div>
</section>

<script src="./dashboard.js" defer></script>

這段 HTML 只建立資料要放的位置。真正的內容由 dashboard.js 讀取 JSON 後填入,所以之後換一份審查報告,不需要重新調整版面。

三、用 JavaScript 把 JSON 轉成畫面

在同一個 web/ 資料夾新增 dashboard.js:

const categoryLabels = {
  security: "資安",
  logic: "邏輯",
  boundary: "邊界條件"
};

const reportFile = document.getElementById("report-file");
const issueCount = document.getElementById("issue-count");
const securityCount = document.getElementById("security-count");
const reportTime = document.getElementById("report-time");
const reportStatus = document.getElementById("report-status");
const findingsList = document.getElementById("findings-list");

async function loadReport() {
  try {
    const response = await fetch("./report.json", { cache: "no-store" });
    if (!response.ok) {
      throw new Error(`讀取 report.json 失敗:HTTP ${response.status}`);
    }

    const report = await response.json();
    if (!report || !Array.isArray(report.findings)) {
      throw new Error("報告格式錯誤:findings 必須是陣列");
    }

    reportFile.textContent = report.file || "未提供檔案名稱";
    reportTime.textContent = report.generatedAt
      ? `產生時間:${report.generatedAt}`
      : "未提供產生時間";
    issueCount.textContent = String(report.findings.length);
    securityCount.textContent = String(
      report.findings.filter((item) => item.category === "security").length
    );

    findingsList.replaceChildren();

    if (report.findings.length === 0) {
      const emptyState = document.createElement("p");
      emptyState.className = "rounded-xl border border-slate-700 p-4 text-slate-300";
      emptyState.textContent = "這份報告沒有列出問題。仍建議確認測試結果,再判斷是否通過。";
      findingsList.appendChild(emptyState);
    }

    report.findings.forEach((finding) => {
      const card = document.createElement("article");
      card.className = "rounded-xl border border-slate-700 p-4";

      const title = document.createElement("h3");
      title.className = "font-semibold text-white";
      const line = Number.isFinite(Number(finding.line))
        ? `第 ${Number(finding.line)} 行`
        : "行號未知";
      const category = categoryLabels[finding.category] || "其他";
      title.textContent = `${line} · ${category}`;

      const reason = document.createElement("p");
      reason.className = "mt-2 text-sm leading-6 text-slate-300";
      reason.textContent = finding.reason || "沒有提供問題說明。";

      const suggestion = document.createElement("p");
      suggestion.className = "mt-2 text-sm leading-6 text-teal-200";
      suggestion.textContent = `修改建議:${finding.suggestion || "請人工確認。"}`;

      card.append(title, reason, suggestion);
      findingsList.appendChild(card);
    });

    reportStatus.textContent = "報告載入完成。請搭配單元測試確認修復結果。";
  } catch (error) {
    reportStatus.textContent = `報告載入失敗:${error.message}`;
    console.error(error);
  }
}

loadReport();

這裡有兩個值得留意的地方。第一,fetch() 讀取 report.json,讓資料和 HTML 分開管理;第二,問題原因與修改建議都使用 textContent 寫入,而不是把 AI 回傳的字串直接交給 innerHTML 解讀。即使分析文字裡包含看起來像 HTML 的內容,也應該把它當成報告文字顯示,而不是讓它成為網頁的一部分。

四、啟動本機頁面並驗收

如果直接用檔案總管開啟 index.html,網址會是 file://。部分瀏覽器會因此限制 fetch() 讀取本機 JSON。可以在終端機切到 web/ 目錄,用 Python 的簡易 HTTP Server 開啟本機頁面:

cd E:\2026ironman\devpulse\web
py -m http.server 8000

接著在瀏覽器開啟 http://localhost:8000。若專案不在上述路徑,請改成自己的 devpulse/web/ 位置;如果環境沒有 Python,也可以使用已安裝的本機靜態頁面伺服器。

驗收時先對照 report.json:

  • 「發現問題」的數量是否等於 findings 的筆數?
  • 「資安問題」數量是否只統計 category 為 security 的項目?
  • 每張卡片顯示的行號、原因和建議是否與 JSON 一致?
  • 把某個 reason 暫時改成 <img src=x onerror=alert(1)>,重新載入後,它應該只會以文字出現在畫面上,不應執行任何事件;測完記得還原示範內容。

五、今天完成了什麼,又還沒完成什麼?

今天把兩個原本分開的成果接在一起了:Genkit 負責產生結構化的審查結果,Stitch 頁面負責把結果呈現給人看。從此不只可以在終端機看到一段 JSON,也能在瀏覽器快速掌握問題數量、分類與修正建議。

不過,這仍是靜態報表展示:目前是把一次審查結果整理成 report.json,頁面載入時再讀取它。它還不會在每次執行 devpulse run 後自動更新,也不是即時監控服務。這個界線要說清楚,才不會把「能顯示資料」誤認成「資料已經自動串流」。

今天也延續了昨天的安全觀念:AI 產生的內容不只要檢查語意,也要注意它進入其他系統後會如何被解讀。把資料當作文字呈現,是一個小改動,卻能避免前端不必要的風險。

明天就是 Day 30。最後一天會把這 30 天碰過的 Gemini API、Zod、Genkit、Stitch 和 Antigravity 整理成一張知識地圖,回顧 DevPulse 從零件到作品的過程,並整理成可以帶走的個人作品集。


上一篇
Day 28:自己的小幫手自己顧:簡單測試 Prompt Injection 防護
系列文
30 天玩轉 Google AI 全家桶:初學者的隨身 Coding 助理養成記 共 29 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言